package Gui.Simulator;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Stroke;
import java.awt.event.ActionEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.util.ArrayList;

import javax.swing.JPanel;
import javax.swing.JScrollPane;

import lejos.robotics.navigation.Move.MoveType;
import simulator.labyrinth.BarCode;
import simulator.labyrinth.Labyrinth;
import simulator.labyrinth.Line;
import simulator.labyrinth.Square;
import simulator.labyrinth.Wall;
import simulator.position.Position;
import simulator.robot.Simulator;
import simulator.robot.SimulatorListener;


public class Board extends JPanel implements MouseListener{

	/**
	 * 
	 */
	private static final long serialVersionUID = -2961644087225708168L;
	private ArrayList<Wall> walls= new ArrayList<Wall>();
	private ArrayList<Line> lines= new ArrayList<Line>();
	
	private Labyrinth labyrinth;
	
	private final int GRID_SIZE = 40;
	private JScrollPane scrollPane;
	private Simulator simulator;
	private ArrayList<Line> doolhofLines = new ArrayList<Line>();
	private SimulatorListener simulatorListener;
	
	public Board(JScrollPane scrollPane) {
		
		setFocusable(true);
		setDoubleBuffered(true);
		this.scrollPane = scrollPane;
		this.addMouseListener(this);
	}

	private ArrayList<Point> pointList = new ArrayList<Point>();

	private double sensorOffset = 0;
	
	
	public void setSimulator(Simulator simulator)
	{
		this.simulator = simulator;
	}
	
	public void setLabyrinth(Labyrinth labyrinth) {
		this.labyrinth = labyrinth;
	}
	
	public Labyrinth getLabyrinth()
	{
		if(labyrinth != null)
			return labyrinth;
		if(this.simulator == null)
			return null;
		return this.simulator.getLabyrinth();
	}

	public Position getPosition()
	{
		if(this.simulator == null)
			return null;
		return this.simulator.getPosition();
	}
	
	@Override
	public void paint(Graphics g) {
		super.paint(g);
		Color color = new Color(191,123,34);
		this.setBackground(color);
		
		drawGrid(g);
		if(this.simulator != null)
		{
			drawArrow(g);
			drawSensor(g);
			if (labyrinth != null)
				drawLabyrinth(labyrinth, g);
			else
				drawLabyrinth(g);		//TODO
		}
		
		g.dispose();
		
		// Sleep 16 ms to get 60 fps
		try {
            Thread.sleep(16);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
		
		repaint();
	}

	private void drawAxis(Graphics g)
	{
		Color color = g.getColor();
		int yScale = 100;
		int xScale = 100;
		g.translate(this.getWidth()/2, this.getHeight()/2); // set center 0, 0

		//draw x-axis
		g.setColor(Color.blue);
		g.drawLine(-this.getWidth() / 2, 0, this.getWidth() / 2, 0);
		for(int i = -100; i < 100; i++)
		{
			g.drawString(""+i*xScale, i*xScale, 15);
			g.drawLine(i*xScale, 0, i*xScale, -10);
		}
		for(int i = -1000; i < 1000; i++)
			g.drawLine(i*xScale/5, 0, i*xScale/5, -5);

		//draw y-axis
		g.drawLine(0, -this.getHeight() / 2, 0, this.getHeight() / 2);
		for(int i = -100; i < 100; i++)
		{
			g.drawString(""+i*yScale, -35, i*yScale);
			g.drawLine(0,i*yScale, 10, i*yScale);
		}
		for(int i = -1000; i < 1000; i++)
			g.drawLine(0,i*yScale/5, 5, i*yScale/5);
		g.setColor(color);
		g.translate(0, 0);
	}

	private void drawGrid(Graphics g)
	{
		Color color = g.getColor();
		g.setColor(Color.gray);
		int yScale = GRID_SIZE*2;
		int xScale = GRID_SIZE*2;
		g.translate(this.getWidth()/2, this.getHeight()/2);
		int horizontalLines = (int) Math.ceil(this.getHeight() / 2 / yScale);
		int verticalLines = (int) Math.ceil(this.getWidth() / 2 / xScale);
		//grijze lijnen = midden van panelen
		for(int i = -horizontalLines; i <= horizontalLines; i++)
			g.drawLine(-this.getWidth() / 2, i * yScale-GRID_SIZE, this.getWidth() / 2, i * yScale-GRID_SIZE);

		for(int i = -verticalLines; i <= verticalLines; i++)
			g.drawLine(i * xScale-GRID_SIZE, -this.getHeight() / 2, i * xScale-GRID_SIZE, this.getHeight() / 2);
		//zwarte lijnen = panelranden
		for(int i = -horizontalLines; i <= horizontalLines; i++)
			g.drawLine(-this.getWidth() / 2, i * yScale-GRID_SIZE*2, this.getWidth() / 2, i * yScale-GRID_SIZE*2);

		for(int i = -verticalLines; i <= verticalLines; i++)
			g.drawLine(i * xScale-GRID_SIZE*2, -this.getHeight() / 2, i * xScale-GRID_SIZE*2, this.getHeight() / 2);
		//g.drawLine(0, -1000, 0, 1000);
		g.setColor(color.BLUE);
		
		Graphics2D g2D = (Graphics2D) g;
	    g2D.setStroke(new BasicStroke(3F));
		for(Line line : doolhofLines)
		    g.drawLine((int)line.getX1(), (int)line.getY1(), (int)line.getX2(), (int)line.getY2());
		
		g.translate(-this.getWidth()/2, -this.getHeight()/2);
		g.setColor(color);
	}

	public void actionPerformed(ActionEvent e) {
		repaint();  
	}


	private void checkScrollView()
	{
		Point viewPortPosition = scrollPane.getViewport().getViewPosition();
		Position position = this.getPosition();
		
		if(viewPortPosition.equals(new Point(0,0))) // Initial viewport center
		{
			int x = this.getWidth() / 2 - scrollPane.getWidth() / 2;
			int y = this.getHeight() / 2 - scrollPane.getHeight()  / 2;
			scrollPane.getViewport().setViewPosition(new Point(x,y));
		}
		else
		{
			Rectangle viewRect = scrollPane.getViewport().getViewRect();
			int viewX = (int) viewPortPosition.getX();
			int viewY = (int) viewPortPosition.getY();
			if(position.getX() + this.getWidth() / 2 > viewRect.getMaxX()) // out of bounds
				viewX += scrollPane.getWidth();
			if(position.getX() + this.getWidth() / 2 < viewRect.getMinX()) // out of bounds
				viewX -= scrollPane.getWidth();

			if(position.getY() + this.getHeight() / 2 > viewRect.getMaxY()) // out of bounds
				viewY += scrollPane.getHeight();
			if(position.getY() + this.getHeight() / 2 < viewRect.getMinY()) // out of bounds
				viewY -= scrollPane.getHeight();

			if(viewX < 0)
				viewX = 0;
			if(viewY < 0)
				viewY = 0;
			if(viewY > this.getHeight())
				viewY = this.getHeight();
			scrollPane.getViewport().setViewPosition(new Point(viewX, viewY));
		}

	}

	private void drawArrow(Graphics g)
	{

		Font font = g.getFont();
		Color color = g.getColor();
		g.setColor(Color.red);

		checkScrollView();
		
		Position position = this.getPosition();

		double x = position.getX() + this.getWidth() / 2;
		x = x % this.getWidth();		
		double y = position.getY() + this.getHeight() / 2;
		y = y % this.getHeight();
		Point p = new Point((int)x,(int)y);
		if(!pointList.contains(p))
			pointList.add(p);

		int r = 20;
		double k = Math.toRadians(120 + position.getOrientation());
		double l = Math.toRadians(240 + position.getOrientation());
		double f = Math.toRadians(position.getOrientation());
		int[] xValues= {(int) (x+0.5*r*Math.cos(k)), (int) (x+0.5*r*Math.cos(l)), (int) (x+r*Math.cos(f))};
		int[] yValues= {(int) (y+0.5*r*Math.sin(k)), (int) (y+0.5*r*Math.sin(l)), (int) (y+r*Math.sin(f))};
		g.fillPolygon(xValues, yValues, 3);

		for(Point point : pointList)
		{
			g.drawOval((int)point.getX(), (int)point.getY(), 2, 2);
		}

		g.setFont(new Font("Arial", Font.BOLD, 15));
		g.drawString("("+(int)position.getX()+", "+(int)position.getY()+")", (int)x + 20, (int)y - 20);
		g.setColor(color);
		g.setFont(font);

	}
	private void drawSensor(Graphics g){
		Position position = this.getPosition();
		checkScrollView();
		Color color = g.getColor();
		g.setColor(Color.blue);
		int r = 12;
		
		double k = Math.toRadians(120 + position.getOrientation()+sensorOffset);
		double l = Math.toRadians(240 + position.getOrientation()+sensorOffset);
		double f = Math.toRadians(position.getOrientation()+sensorOffset);
		double x = (position.getX() + this.getWidth() / 2);//position.getX() + Math.cos(Math.toRadians(position.getOrientation()))*4;
		x = x % this.getWidth();		
		double y = (position.getY() + this.getHeight() / 2);//position.getY()+Math.sin(Math.toRadians(position.getOrientation()))*4;
		y = y % this.getHeight();
		//int r = 6;
		//double k = Math.toRadians(120 + position.getOrientation()+sensorOffset);
		//double l = Math.toRadians(240 + position.getOrientation()+sensorOffset);
		//double f = Math.toRadians(position.getOrientation()+sensorOffset);
		int[] xValues= {(int) (x+0.5*r*Math.cos(k)), (int) (x+0.5*r*Math.cos(l)), (int) (x+r*Math.cos(f))};
		int[] yValues= {(int) (y+0.5*r*Math.sin(k)), (int) (y+0.5*r*Math.sin(l)), (int) (y+r*Math.sin(f))};
		g.fillPolygon(xValues, yValues, 3);
		g.setColor(color);

	}
	public void setSensorOffset(double angle){
		this.sensorOffset = angle;
	}


	private void drawLabyrinth(Labyrinth labyrinth, Graphics g) {
		
		checkScrollView();
		g.translate(this.getWidth()/2, this.getHeight()/2);
	
		for (Square square: labyrinth.getSquares()) {
			for (Wall wall: square.getWalls()){
				drawWall(wall,g);
			}
			for (Line line: square.getLines()){
				drawLine(line,g);
			}
		}
	}
	
	private void drawLabyrinth(Graphics g) {
		if (getLabyrinth() == null)
			return;

		checkScrollView();
		Labyrinth labyrinth = this.getLabyrinth();
		Position position = this.getPosition();

		g.translate(this.getWidth()/2, this.getHeight()/2);

		if (!simulator.getSensorMotor().isMoving() && !simulator.getMovement().getMoveType().equals(MoveType.ROTATE)) {
			Wall w = simulatorListener.getNearestWall();
			if (w != null && !walls.contains(w))
				walls.add(w);
		}
		
		simulator.position.Point positionOfLightSensor = simulator.getPositionOfSensor();
		Square square = labyrinth.getSquareFromPoint(positionOfLightSensor);
		
		if (square.getLines() != null)
				for (Line line : square.getLines()) {
					if (line.findIntersection(positionOfLightSensor))
						lines.add(line);
				}
		for (Wall wall: walls){
			drawWall(wall,g);
			//System.out.println(wall.getStartPosition().getX()+"/"+wall.getStartPosition().getY()+"//"+wall.getEndPosition().getX()+"/"+wall.getEndPosition().getY());
		}
		for (Line line: lines){
			drawLine(line,g);
			//System.out.println(line.getStartPosition().getX()+"/"+line.getStartPosition().getY()+"//"+line.getEndPosition().getX()+"/"+line.getEndPosition().getY());
		}

	}

	public void drawWall(Wall wall, Graphics g) {
		Color color = g.getColor();
		Font font = g.getFont();
		g.setColor(Color.green);
		g.drawLine((int)wall.getStartPosition().getX(), (int)wall.getStartPosition().getY(),(int)wall.getEndPosition().getX(), (int)wall.getEndPosition().getY());
		//System.out.println(wall.getStartPosition().getX()+"/"+ wall.getStartPosition().getY()+"//"+wall.getEndPosition().getX()+"/"+(int)wall.getEndPosition().getY());
		g.setColor(color);
		g.setFont(font);
	}
	
	public void drawLine(Line line, Graphics g){
		Color color = g.getColor();
		Graphics2D g2D = (Graphics2D) g;      
		Stroke oldStroke = g2D.getStroke();
	    g2D.setStroke(new BasicStroke(2F));  // set stroke width of 2
	 
		if (line.getColour().toString().equals("White"))
			g.setColor(Color.white);
		else
			g.setColor(Color.BLACK);
		g.drawLine((int)line.getStartPosition().getX(), (int)line.getStartPosition().getY(),(int)line.getEndPosition().getX(), (int)line.getEndPosition().getY());
		//System.out.println(wall.getStartPosition().getX()+"/"+ wall.getStartPosition().getY()+"//"+wall.getEndPosition().getX()+"/"+(int)wall.getEndPosition().getY());
		g.setColor(color);
		g2D.setStroke(oldStroke);
	}
	
	public void rotateSensorTo(double degrees){
		this.sensorOffset = degrees;
	}
	
	public void setSimulatorListener(SimulatorListener simulatorListener) {
		this.simulatorListener = simulatorListener;
	}

	@Override
	public void mouseClicked(MouseEvent e) {
		// TODO Auto-generated method stub
		double x = (e.getX() - this.getWidth() / 2);
		double y = (e.getY() - this.getWidth() / 2);
		
		Line newLine = null;

		if( Math.abs((x / 40) - ((int)x / 40)) <= 0.1)
		{
			double lineX1 = Math.round(x / 40) * 40;
			double lineX2 = lineX1;
			double lineY1 = (((int)y / 40) * 40);
			double lineY2 = lineY1 + (40*Math.signum(y));
			newLine = new Line((int)lineX1, (int)lineY1, (int)lineX2, (int)lineY2);			
		}
		
		if( Math.abs((y / 40) - ((int)y / 40)) <= 0.1)
		{
			double lineY1 = Math.round(y / 40) * 40;
			double lineY2 = lineY1;
			double lineX1 = (((int)x / 40) * 40);
			double lineX2 = lineX1 + (40*Math.signum(x));
			newLine = new Line((int)lineX1, (int)lineY1, (int)lineX2, (int)lineY2);
		}
		
		if(newLine != null)
		{
			if(doolhofLines.contains(newLine))
			{
				if(e.getButton() == 3)
					doolhofLines.remove(newLine);
			}
			else 
			{
		
				if(e.getButton() == 1)
					doolhofLines.add(newLine);
			}
		}
			
		
	}

	@Override
	public void mousePressed(MouseEvent e) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void mouseReleased(MouseEvent e) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void mouseEntered(MouseEvent e) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void mouseExited(MouseEvent e) {
		// TODO Auto-generated method stub
		
	}
	
}